<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
    <link rel="icon" href="data:;base64,=">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
</head>
<body>
<input type="file" name="file" id="file">
<text id="msgtext"></text>
</body>
<script>
    const baseUrl = "http://127.0.0.1:8888/api/file/"
    /**
     * 计算文件的md5值
     * @param file 文件
     */
    function calculateFileMd5(file) {
        return calculateFileMd5Chunk(file, 2097152);
    }

    /**
     * 分片计算文件的md5值
     * @param file 文件
     * @param chunkSize 分片大小
     */
    function calculateFileMd5Chunk(file, chunkSize) {
        return new Promise((resolve, reject) => {
            let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
            let chunks = Math.ceil(file.size / chunkSize);
            let currentChunk = 0;
            let spark = new SparkMD5.ArrayBuffer();
            let fileReader = new FileReader();
            fileReader.onload = function (e) {
                spark.append(e.target.result);
                currentChunk++;
                if (currentChunk < chunks) {
                    loadNext();
                } else {
                    let md5 = spark.end();
                    resolve(md5);
                }
            };
            fileReader.onerror = function (e) {
                reject(e);
            };
            function loadNext() {
                let start = currentChunk * chunkSize;
                let end = start + chunkSize;
                if (end > file.size) {
                    end = file.size;
                }
                fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
            }
            loadNext();
        });
    }

    /**
     * 获取文件的后缀名
     * @param fileName 文件名
     */
    function getFileType(fileName) {
        return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
    }

    /**
     * 根据文件的md5值判断文件是否已经上传
     * @param md5 文件的md5
     * @param file 准备上传的文件
     */
    function checkMd5(md5, file) {
        $.ajax({
            url: baseUrl + "checkSplitFile",
            type: "get",
            data: {
                md5: md5
            },
            async: true,
            dataType: "json",
            success: function (msg) {
                if (msg.code === 200) {
                    console.log("文件已经存在")
                    $('#msgtext').html('文件已存在: '+ msg.fileUrl);
                } else if (msg.code === 204) {
                    console.log("文件不存在需要上传")
                    postFile(file, 0, md5);
                } else {
                    console.log('未知错误');
                }
            }
        })
    }

    /**
     * 分片上传
     * @param file 上传的文件
     * @param i 第几分片，从0开始
     * @param md5 文件的md5值
     */
    function postFile(file, i, md5) {
        let name = file.name,                           // 文件名
            size = file.size,                           // 总大小shardSize = 2 * 1024 * 1024,
            shardSize = 5 * 1024 * 1024,                // 以5MB为一个分片,每个分片的大小
            shardCount = Math.ceil(size / shardSize);   // 总片数
        if (i >= shardCount) {
            return;
        }
        let start = i * shardSize;
        let end = start + shardSize;
        let packet = file.slice(start, end);            // 将文件进行切片
        let form = new FormData();
        form.append("md5", md5);                        // 前端生成uuid作为标识符
        form.append("data", packet);                    // slice方法用于切出文件的一部分
        form.append("name", name);
        form.append("totalSize", size);
        form.append("total", shardCount);               // 总片数
        form.append("index", i + 1);                    // 当前是第几片
        $.ajax({
            url: baseUrl + "uploadSplitFile",
            type: "post",
            data: form,
            async: true,
            dataType: "json",
            processData: false,
            contentType: false,
            success: function (msg) {
                if (msg.code === 201) {
                    form = '';
                    i++;
                    postFile(file, i, md5);
                } else if (msg.code === 203) {
                    form = '';
                    setInterval(function () {
                        postFile(file, i, md5)
                    }, 2000);
                } else if (msg.code === 202) {
                    merge(shardCount, name, md5, getFileType(file.name), file.size)
                    console.log("上传成功");
                } else {
                    console.log('未知错误');
                }
            }
        })
    }

    /**
     * 合并文件
     * @param shardCount 分片数
     * @param fileName 文件名
     * @param md5 文件md值
     * @param fileType 文件类型
     * @param fileSize 文件大小
     */
    function merge(shardCount, fileName, md5, fileType, fileSize) {
        $.ajax({
            url: baseUrl + "mergeSplitFile",
            type: "get",
            data: {
                shardCount: shardCount,
                fileName: fileName,
                md5: md5,
                fileType: fileType,
                fileSize: fileSize
            },
            async: true,
            dataType: "json",
            success: function (msg) {
                $('#msgtext').html('文件上传成功: '+ msg.fileName);
            }
        })
    }

    // 浏览器加载文件后, 计算文件的md5值
    document.getElementById("file").addEventListener("change", function () {
        $('#msgtext').html('待上传');
        let file = this.files[0];
        calculateFileMd5(file).then(e => {
            let md5 = e;
            checkMd5(md5, file)
        }).catch(e => {
            console.error(e);
        });
    });

    $('#msgtext').html('待上传');
</script>
</html>
